﻿using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Windows.Controls;
using System.Windows.Media;
using LukasKopenec.LocalizationSupport;
using Notoric.Commanding.CommandBase;
using Notoric.Model.Elements;
using Notoric.Model.Packaging;
using Notoric.ViewModel.Internal;

namespace Notoric.ViewModel.Elements
{
    /// <summary>
    /// Adapts the <see cref="ProjectPart"/> and provides additional functionality concerning
    /// the manipulation of the project package.
    /// </summary>
    /// <remarks>
    /// <para>
    /// This class provides functionallity for automatical background saving of the open project package.
    /// When a project file is open, first its copy is made and it is actually the copy that is open.
    /// When <see cref="AutoSave"/> is called, the changes are saved to the temporary copy.
    /// When <see cref="Save"/> is called, the changes are saved to the temporary copy and then
    /// the copy replaces the original file.
    /// </para>
    /// <para>
    /// This class also implements the <see cref="IProjectExplorerItem"/> interface and thus can
    /// be directly visualized in the Project Explorer window.
    /// </para>
    /// </remarks>
    public class Project : ViewModelElement, IProjectExplorerItem
    {
        #region Constructors

        /// <summary>
        /// Creates a new <see cref="Project"/> instance.
        /// </summary>
        /// <param name="projectPackage">
        /// <para>
        /// Type: <see cref="Package"/>
        /// </para>
        /// <para>
        /// The project package.
        /// </para>
        /// </param>
        /// <param name="fileName">
        /// <para>
        /// Type: <see cref="System.String"/>
        /// </para>
        /// <para>
        /// The filename of the project package.
        /// This may be <see langword="null"/> if the project was just created.
        /// </para>
        /// </param>
        /// <param name="tempFileName">
        /// <para>
        /// Type: <see cref="System.String"/>
        /// </para>
        /// <para>
        /// The filename of the temporary project package copy.
        /// This file must exist.
        /// </para>
        /// </param>
        /// <exception cref="NullReferenceException">
        /// <paramref name="projectPackage"/> is <see langword="null"/>.<br/>
        /// The ProjectPart of the <paramref name="projectPackage"/> is immediately
        /// passed to the base (<see cref="ViewModelElement"/>) constructor, so there is no occasion
        /// to react with a more appropriate exception.
        /// </exception>
        /// <exception cref="FileNotFoundException">
        /// <paramref name="tempFileName"/> does not exist.
        /// </exception>
        protected Project(Package projectPackage, string fileName, string tempFileName)
            : base(
                projectPackage.ProjectPart,
                null, 
                new CommandStack(), 
                new CommandStack()
            )
        {
            if (!File.Exists(tempFileName))
                throw new FileNotFoundException();

            this.projectPackage = projectPackage;
            this.fileName = fileName;
            this.tempFileName = tempFileName;

            scores = new ObservableCollection<Score>();
            resources = new ObservableCollection<Resource>();
            compositions = new ObservableCollection<Composition>();

            projectExplorerHelper = new ProjectExplorerItemHelper(null, this);
            projectExplorerHelper.SetIcon(EmbeddedBitmapLoader.Load("Pictures.ProjectExplorer.Project.png"));
            projectExplorerHelper.PropertyChanged += new PropertyChangedEventHandler(projectExplorerHelper_PropertyChanged);

            projectExplorerHelper.CreateRootedHierarchy(((ProjectElement)AdaptedElement).Scores,
                (string)LocalizationManager.GetValue("ProjectExplorer.Labels.Scores"),
                CreateScoreLeafElement,
                CreateScoreDirectoryMenu()
                );
            projectExplorerHelper.CreateRootedHierarchy(((ProjectElement)AdaptedElement).Resources,
                (string)LocalizationManager.GetValue("ProjectExplorer.Labels.Resources"),
                CreateResourceLeafElement,
                CreateResourceDirectoryMenu()
                );
            projectExplorerHelper.CreateRootedHierarchy(((ProjectElement)AdaptedElement).Compositions,
                (string)LocalizationManager.GetValue("ProjectExplorer.Labels.Compositions"),
                CreateCompositionLeafElement,
                CreateCompositionDirectoryMenu()
                );

            ((ProjectElement)AdaptedElement).Scores.CollectionChanged += new EventHandler<Model.Collections.NotifyCollectionChangedEventArgs>(Scores_CollectionChanged);
            ((ProjectElement)AdaptedElement).Resources.CollectionChanged += new EventHandler<Model.Collections.NotifyCollectionChangedEventArgs>(Resources_CollectionChanged);
            ((ProjectElement)AdaptedElement).Compositions.CollectionChanged += new EventHandler<Model.Collections.NotifyCollectionChangedEventArgs>(Compositions_CollectionChanged);

            projectPackage.PropertyChanged += new PropertyChangedEventHandler(projectPackage_PropertyChanged);
        }

        #endregion

        #region ViewModelElement Members

        /// <inheritdoc/>
        public override string ElementNameKey
        {
            get { return elementNameKey; }
        }

        #endregion

        #region IProjectExplorerItem Members

        /// <inheritdoc/>
        public ContextMenu ContextMenu
        {
            get { return projectExplorerHelper.ContextMenu; }
        }

        /// <inheritdoc/>
        public string DisplayName
        {
            get { return Name; }
        }

        /// <inheritdoc/>
        public ImageSource Icon
        {
            get { return projectExplorerHelper.Icon; }
        }

        /// <inheritdoc/>
        public bool IsExpanded
        {
            get
            {
                return projectExplorerHelper.IsExpanded;
            }
            set
            {
                projectExplorerHelper.IsExpanded = value;
            }
        }

        /// <inheritdoc/>
        public bool IsInEditMode
        {
            get { return projectExplorerHelper.IsInEditMode; }
            set { projectExplorerHelper.IsInEditMode = value; }
        }

        /// <inheritdoc/>
        public bool IsSelected
        {
            get
            {
                return projectExplorerHelper.IsSelected;
            }
            set
            {
                projectExplorerHelper.IsSelected = value;
            }
        }

        /// <inheritdoc/>
        public string Name
        {
            get { return ((ProjectElement)AdaptedElement).Name; }
            set
            {
                ((ProjectElement)AdaptedElement).Name = value;
                RaisePropertyChanged("Name");
                RaisePropertyChanged("DisplayName");
            }
        }

        /// <inheritdoc/>
        public IProjectExplorerItem Parent
        {
            get { return projectExplorerHelper.Parent; }
            set { projectExplorerHelper.Parent = value; }
        }

        /// <inheritdoc/>
        public ObservableCollection<IProjectExplorerItem> Children
        {
            get { return projectExplorerHelper.Children; }
        }

        #endregion

        #region Event Handlers
        
        /// <summary>
        /// Occurs when the collection of compositions of the adapted project has changed.
        /// </summary>
        /// <param name="sender">Object that has sent the event</param>
        /// <param name="e">Event data</param>
        private void Compositions_CollectionChanged(object sender, Model.Collections.NotifyCollectionChangedEventArgs e)
        {
            switch (e.Action)
            {
                case Model.Collections.NotifyCollectionChangedAction.Add:
                {
                    int index = e.NewStartingIndex;
                    foreach (Model.Collections.PathElement pathElement in e.NewItems)
                    {
                        var compositionPart = (Model.Packaging.CompositionPart)projectPackage.GetPart(pathElement.URI);
                        var compositionName = pathElement.VirtualPath.Substring(pathElement.VirtualPath.LastIndexOf('/') + 1);
                        var composition = new Composition(compositionPart, this, compositionName);

                        compositions.Insert(index++, composition);
                        projectExplorerHelper.AddToHierarchy(pathElement.VirtualPath, composition, GetRootDirectory(RootDirectory.Compositions), CreateCompositionDirectoryMenu());
                    }
                    break;
                }
                case Model.Collections.NotifyCollectionChangedAction.Remove:
                    foreach (Model.Collections.PathElement pathElement in e.OldItems)
                    {
                        var compositionItem = ProjectExplorerItemHelper.FindItem(pathElement.VirtualPath, GetRootDirectory(RootDirectory.Compositions));
                        if (compositionItem != null)
                        {
                            compositionItem.Parent.Children.Remove(compositionItem);
                            compositions.Remove((Composition)compositionItem);
                        }
                    }
                    break;
            }
        }

        /// <summary>
        /// Occurs when the collection of resources of the adapted project has changed.
        /// </summary>
        /// <param name="sender">Object that has sent the event</param>
        /// <param name="e">Event data</param>
        private void Resources_CollectionChanged(object sender, Model.Collections.NotifyCollectionChangedEventArgs e)
        {
            switch (e.Action)
            {
                case Model.Collections.NotifyCollectionChangedAction.Add:
                {
                    int index = e.NewStartingIndex;
                    foreach (Model.Collections.PathElement pathElement in e.NewItems)
                    {
                        var resourcePart = (Model.Packaging.ResourcePart)projectPackage.GetPart(pathElement.URI);
                        var resourceName = pathElement.VirtualPath.Substring(pathElement.VirtualPath.LastIndexOf('/') + 1);
                        var resource = new Resource(resourcePart, this, resourceName);

                        resources.Insert(index++, resource);
                        projectExplorerHelper.AddToHierarchy(pathElement.VirtualPath, resource, GetRootDirectory(RootDirectory.Resources), CreateResourceDirectoryMenu());
                    }
                    break;
                }
                case Model.Collections.NotifyCollectionChangedAction.Remove:
                    foreach (Model.Collections.PathElement pathElement in e.OldItems)
                    {
                        var resourceItem = ProjectExplorerItemHelper.FindItem(pathElement.VirtualPath, GetRootDirectory(RootDirectory.Resources));
                        if (resourceItem != null)
                        {
                            resourceItem.Parent.Children.Remove(resourceItem);
                            resources.Remove((Resource)resourceItem);
                        }
                    }
                    break;
            }
        }

        /// <summary>
        /// Occurs when the collection of scores of the adapted project has changed.
        /// </summary>
        /// <param name="sender">Object that has sent the event</param>
        /// <param name="e">Event data</param>
        private void Scores_CollectionChanged(object sender, Model.Collections.NotifyCollectionChangedEventArgs e)
        {
            switch (e.Action)
            {
                case Model.Collections.NotifyCollectionChangedAction.Add:
                {
                    int index = e.NewStartingIndex;
                    foreach (Model.Collections.PathElement pathElement in e.NewItems)
                    {
                        var scorePart = (Model.Packaging.ScorePart)projectPackage.GetPart(pathElement.URI);
                        var scoreName = pathElement.VirtualPath.Substring(pathElement.VirtualPath.LastIndexOf('/') + 1);
                        var score = new Score(scorePart, scoreName, this, UndoStack, RedoStack);

                        scores.Insert(index++, score);
                        projectExplorerHelper.AddToHierarchy(pathElement.VirtualPath, score, GetRootDirectory(RootDirectory.Scores), CreateScoreDirectoryMenu());
                    }
                    break;
                }
                case Model.Collections.NotifyCollectionChangedAction.Remove:
                    foreach (Model.Collections.PathElement pathElement in e.OldItems)
                    {
                        var scoreItem = ProjectExplorerItemHelper.FindItem(pathElement.VirtualPath, GetRootDirectory(RootDirectory.Scores));
                        if (scoreItem != null)
                        {
                            scoreItem.Parent.Children.Remove(scoreItem);
                            scores.Remove((Score)scoreItem);
                        }
                    }
                    break;
            }
        }

        /// <summary>
        /// Occurs when the value of some property of the <see cref="projectExplorerHelper"/> has changed.
        /// </summary>
        /// <remarks>
        /// <b>Operation:</b><br/>
        /// The handler just resends the event via its own <see cref="ViewModelElement.PropertyChanged"/> event.
        /// </remarks>
        /// <param name="sender">
        /// <para>
        /// Type: <see cref="System.Object"/>
        /// </para>
        /// <para>
        /// The object that has sent the event.
        /// </para>
        /// </param>
        /// <param name="e">
        /// <para>
        /// Type: <see cref="PropertyChangedEventArgs"/>
        /// </para>
        /// <para>
        /// Event data.
        /// </para>
        /// </param>
        void projectExplorerHelper_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            RaisePropertyChanged(e);
        }

        /// <summary>
        /// Occurs when the value of some property of the <see cref="projectPackage"/> has changed.
        /// </summary>
        /// <param name="sender">Object that has sent the event</param>
        /// <param name="e">Event data</param>
        private void projectPackage_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "IsModified")
                RaisePropertyChanged("IsModified");
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// Creates a contextu menu template for the compositions directories.
        /// </summary>
        /// <returns>The created menu</returns>
        private ContextMenu CreateCompositionDirectoryMenu()
        {
            var menu = new ContextMenu();

            var mitem = new MenuItem();
            mitem.Icon = EmbeddedBitmapLoader.LoadImage("Pictures.ProjectExplorer.NewDocument.png", 16, 16);
            mitem.Header = (string)LocalizationManager.GetValue("ProjectExplorer.CommandTitles.NewComposition");
            mitem.Command = ProjectExplorerCommands.NewCompositionCommand;
            menu.Items.Add(mitem);

            mitem = new MenuItem();
            mitem.Icon = EmbeddedBitmapLoader.LoadImage("Pictures.ProjectExplorer.NewFolder.png", 16, 16);
            mitem.Header = (string)LocalizationManager.GetValue("ProjectExplorer.CommandTitles.NewFolder");
            mitem.Command = ProjectExplorerCommands.NewDirectoryCommand;
            menu.Items.Add(mitem);

            mitem = new MenuItem();
            mitem.Header = (string)LocalizationManager.GetValue("ProjectExplorer.CommandTitles.Rename");
            mitem.Command = ProjectExplorerCommands.RenameCommand;
            menu.Items.Add(mitem);

            menu.Items.Add(new Separator());

            mitem = new MenuItem();
            mitem.Icon = EmbeddedBitmapLoader.LoadImage("Pictures.ProjectExplorer.Delete.png", 16, 16);
            mitem.Header = (string)LocalizationManager.GetValue("ProjectExplorer.CommandTitles.DeleteFolder");
            mitem.Command = ProjectExplorerCommands.DeleteCommand;
            menu.Items.Add(mitem);

            return menu;
        }

        /// <summary>
        /// Creates a composition that will adapt a given composition part under a given name.
        /// </summary>
        /// <remarks>
        /// This method is called from <see cref="ProjectExplorerItemHelper.CreateRootedHierarchy"/>.
        /// </remarks>
        /// <param name="compositionName">Name of the composition (taken from its virtual path)</param>
        /// <param name="compositionPartUri">URI of the adapted composition part</param>
        /// <returns>Created <see cref="Composition"/> instance</returns>
        private IProjectExplorerItem CreateCompositionLeafElement(string compositionName, Uri compositionPartUri)
        {
            var compositionPart = (CompositionPart)projectPackage.GetPart(compositionPartUri);
            var composition = new Composition(compositionPart, this, compositionName);

            compositions.Add(composition);

            return composition;
        }

        /// <summary>
        /// Creates a contextu menu template for the resources directories.
        /// </summary>
        /// <returns>The created menu</returns>
        private ContextMenu CreateResourceDirectoryMenu()
        {
            var menu = new ContextMenu();

            var mitem = new MenuItem();
            mitem.Header = (string)LocalizationManager.GetValue("ProjectExplorer.CommandTitles.NewResource");
            mitem.Command = ProjectExplorerCommands.NewResourceCommand;
            menu.Items.Add(mitem);

            mitem = new MenuItem();
            mitem.Icon = EmbeddedBitmapLoader.LoadImage("Pictures.ProjectExplorer.NewFolder.png", 16, 16);
            mitem.Header = (string)LocalizationManager.GetValue("ProjectExplorer.CommandTitles.NewFolder");
            mitem.Command = ProjectExplorerCommands.NewDirectoryCommand;
            menu.Items.Add(mitem);

            mitem = new MenuItem();
            mitem.Header = (string)LocalizationManager.GetValue("ProjectExplorer.CommandTitles.Rename");
            mitem.Command = ProjectExplorerCommands.RenameCommand;
            menu.Items.Add(mitem);

            menu.Items.Add(new Separator());

            mitem = new MenuItem();
            mitem.Icon = EmbeddedBitmapLoader.LoadImage("Pictures.ProjectExplorer.Delete.png", 16, 16);
            mitem.Header = (string)LocalizationManager.GetValue("ProjectExplorer.CommandTitles.DeleteFolder");
            mitem.Command = ProjectExplorerCommands.DeleteCommand;
            menu.Items.Add(mitem);

            return menu;
        }

        /// <summary>
        /// Creates a resource that will adapt a given resource part under a given name.
        /// </summary>
        /// <remarks>
        /// This method is called from <see cref="ProjectExplorerItemHelper.CreateRootedHierarchy"/>.
        /// </remarks>
        /// <param name="resourceName">Name of the resource (taken from its virtual path)</param>
        /// <param name="resourcePartUri">URI of the adapted resource part</param>
        /// <returns>Created <see cref="Resource"/> instance</returns>
        private IProjectExplorerItem CreateResourceLeafElement(string resourceName, Uri resourcePartUri)
        {
            var resourcePart = (ResourcePart)projectPackage.GetPart(resourcePartUri);
            var resource = new Resource(resourcePart, this, resourceName);

            resources.Add(resource);

            return resource;
        }

        /// <summary>
        /// Creates a contextu menu template for the scores directories.
        /// </summary>
        /// <returns>The created menu</returns>
        private ContextMenu CreateScoreDirectoryMenu()
        {
            var menu = new ContextMenu();
            
            var mitem = new MenuItem();
            mitem.Icon = EmbeddedBitmapLoader.LoadImage("Pictures.ProjectExplorer.NewDocument.png", 16, 16);
            mitem.Header = (string)LocalizationManager.GetValue("ProjectExplorer.CommandTitles.NewScore");
            mitem.Command = ProjectExplorerCommands.NewScoreCommand;
            menu.Items.Add(mitem);

            mitem = new MenuItem();
            mitem.Icon = EmbeddedBitmapLoader.LoadImage("Pictures.ProjectExplorer.NewFolder.png", 16, 16);
            mitem.Header = (string)LocalizationManager.GetValue("ProjectExplorer.CommandTitles.NewFolder");
            mitem.Command = ProjectExplorerCommands.NewDirectoryCommand;
            menu.Items.Add(mitem);

            mitem = new MenuItem();
            mitem.Header = (string)LocalizationManager.GetValue("ProjectExplorer.CommandTitles.Rename");
            mitem.Command = ProjectExplorerCommands.RenameCommand;
            menu.Items.Add(mitem);

            menu.Items.Add(new Separator());

            mitem = new MenuItem();
            mitem.Icon = EmbeddedBitmapLoader.LoadImage("Pictures.ProjectExplorer.Delete.png", 16, 16);
            mitem.Header = (string)LocalizationManager.GetValue("ProjectExplorer.CommandTitles.DeleteFolder");
            mitem.Command = ProjectExplorerCommands.DeleteCommand;
            menu.Items.Add(mitem);

            return menu;
        }

        /// <summary>
        /// Creates a score that will adapt a given score part under a given name.
        /// </summary>
        /// <remarks>
        /// This method is called from <see cref="ProjectExplorerItemHelper.CreateRootedHierarchy"/>.
        /// </remarks>
        /// <param name="scoreName">Name of the score (taken from its virtual path)</param>
        /// <param name="scorePartUri">URI of the adapted score part</param>
        /// <returns>Created <see cref="Score"/> instance</returns>
        private IProjectExplorerItem CreateScoreLeafElement(string scoreName, Uri scorePartUri)
        {
            var scorePart = (ScorePart)projectPackage.GetPart(scorePartUri);
            var score = new Score(scorePart, scoreName, this, UndoStack, RedoStack);

            scores.Add(score);
            
            return score;
        }

        #endregion

        #region Protected Methods

        /// <summary>
        /// Generates a unique file name for the temporary project package copy.
        /// </summary>
        /// <param name="baseName">
        /// <para>
        /// Type: <see cref="System.String"/>
        /// </para>
        /// <para>
        /// The base of the file name.
        /// This can be any string, but preferably it should be the file name of the original package.
        /// The method will generate a name on the same path as <paramref name="baseName"/> and will decorate
        /// the name of the original file as necessary in order to obtain a unique file name.
        /// </para>
        /// </param>
        /// <returns>
        /// <para>
        /// Type: <see cref="System.String"/>
        /// </para>
        /// <para>
        /// The generated file name.
        /// </para>
        /// </returns>
        protected static string GenerateUniqueName(string baseName)
        {
            if (baseName == null)
                throw new ArgumentNullException("baseName");
            
            string name, baseDirectory, tempName;

            name = Path.GetFileName(baseName);
            baseDirectory = Path.GetDirectoryName(baseName);

            int counter = 0;

            do
            {
                string prefix = "~$";
                if (counter > 0)
                    prefix += counter.ToString();

                if (baseDirectory != null && baseDirectory.Length > 0)
                    tempName = Path.Combine(baseDirectory, prefix + name);
                else
                    tempName = prefix + name;

                ++counter;
            } while (File.Exists(tempName));

            return tempName;
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Saves the project to the temporary file.
        /// </summary>
        public void AutoSave()
        {
            projectPackage.Flush();
        }

        /// <summary>
        /// Closes the project.
        /// </summary>
        public void Close()
        {
            projectPackage.Close();
            stackPackage.Close();

            File.Delete(tempFileName);
            File.Delete(stackFileName);
        }

        /// <summary>
        /// Creates a new blank project in the current working directory.
        /// </summary>
        /// <returns>
        /// <para>
        /// Type: <see cref="Project"/>
        /// </para>
        /// <para>
        /// A view-model <see cref="Project"/> instance that adapts the created project package.
        /// </para>
        /// </returns>
        public static Project CreateNew()
        {
            var tempFileName = GenerateUniqueName("NewProject.ntp");

            Package pkg = Package.Open(tempFileName);
            File.SetAttributes(tempFileName, FileAttributes.Archive | FileAttributes.Hidden);

            var stackPath = GenerateUniqueName("NewProject_Stack.ntp");
            var stackPackage = System.IO.Packaging.Package.Open(stackPath);
            File.SetAttributes(stackPath, FileAttributes.Archive |  FileAttributes.Hidden);
            
            return new Project(pkg, null, tempFileName)
            {
                stackFileName = stackPath,
                stackPackage = stackPackage
            };
        }

        /// <summary>
        /// Gets the root directory of a given project item type.
        /// </summary>
        /// <param name="itemType">
        /// <para>
        /// Type: <see cref="Directory"/>
        /// </para>
        /// <para>
        /// The type of project item for which the directory is requested.
        /// </para>
        /// </param>
        /// <returns>
        /// <para>
        /// Type: <see cref="Directory"/>
        /// </para>
        /// <para>
        /// The root directory that holds the requested item.
        /// </para>
        /// </returns>
        public Directory GetRootDirectory(RootDirectory itemType)
        {
            return Children[(int)itemType] as Directory;
        }

        /// <summary>
        /// Opens a project at a given path and filename.
        /// </summary>
        /// <param name="fileName">
        /// <para>
        /// Type: <see cref="System.String"/>
        /// </para>
        /// <para>
        /// The path and filename of the project.
        /// </para>
        /// </param>
        /// <returns>
        /// <para>
        /// Type: <see cref="Project"/>
        /// </para>
        /// <para>
        /// A view-model <see cref="Project"/> instance that adapts the open project package.
        /// </para>
        /// </returns>
        public static Project Open(string fileName)
        {
            if (!File.Exists(fileName))
                throw new FileNotFoundException();

            var tempFileName = GenerateUniqueName(fileName);
            File.Copy(fileName, tempFileName);
            File.SetAttributes(tempFileName, FileAttributes.Archive | FileAttributes.Hidden);

            var stackPath = GenerateUniqueName(Path.GetDirectoryName(fileName) + Path.GetFileNameWithoutExtension(fileName) + "_Stack" + Path.GetExtension(fileName));
            var stackPackage = System.IO.Packaging.Package.Open(stackPath);
            File.SetAttributes(stackPath, FileAttributes.Archive | FileAttributes.Hidden);

            Package pkg = Package.Open(tempFileName);

            return new Project(pkg, fileName, tempFileName)
            {
                stackFileName = stackPath,
                stackPackage = stackPackage
            };
        }

        /// <summary>
        /// Saves the project on the permanent storage.
        /// </summary>
        public void Save()
        {
            AutoSave();

            if (File.Exists(fileName))
                File.Delete(fileName);
            File.Copy(tempFileName, fileName);
            File.SetAttributes(fileName, FileAttributes.Normal);
        }

        /// <summary>
        /// Suggests a file name for this project.
        /// </summary>
        /// <returns></returns>
        public string SuggestFileName()
        {
            var name = Name;
            foreach (var ch in Path.GetInvalidFileNameChars())
                name.Replace(ch, '_');

            return name;
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets the collection of compositions in this project.
        /// </summary>
        public ObservableCollection<Composition> Compositions
        {
            get { return compositions; }
        }

        /// <summary>
        /// Gets or sets the name and path of the file that is used
        /// as the physical storage for the project.
        /// </summary>
        public string FileName
        {
            get { return fileName; }
            set 
            {
                fileName = value;
                RaisePropertyChanged("FileName");
            }
        }

        /// <summary>
        /// Gets or sets the value indicating whether the project has unsaved changes or not.
        /// </summary>
        public bool IsModified
        {
            get { return projectPackage.IsModified; }
            set { projectPackage.IsModified = value; }
        }

        /// <summary>
        /// Gets the collection of resources in this project.
        /// </summary>
        public ObservableCollection<Resource> Resources
        {
            get { return resources; }
        }

        /// <summary>
        /// Gets the collection of scores in this project.
        /// </summary>
        public ObservableCollection<Score> Scores
        {
            get { return scores; }
        }

        /// <summary>
        /// Gets the temporary stack file for this project.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="System.IO.Packaging.Package"/>
        /// </para>
        /// <para>
        /// The temporary stack file for this project.<br/>
        /// The stack file serves as a temporary storage for deleted project package parts
        /// that could be undeleten.
        /// </para>
        /// </value>
        public System.IO.Packaging.Package StackFile
        {
            get { return stackPackage; }
        }

        /// <summary>
        /// Gets the name and path of the temporary project file.
        /// </summary>
        public string TempFileName
        {
            get { return tempFileName; }
        }

        #endregion

        #region Private Fields

        /// <summary>
        /// Holds the collection of compositions in this project.
        /// </summary>
        private ObservableCollection<Composition> compositions;
        /// <summary>
        /// Holds the dictionary key that identifies a user-friendly name of this element.
        /// </summary>
        private const string elementNameKey = "ViewModel.ElementNames.Project";
        /// <summary>
        /// Holds the name of the project file that is currently open.
        /// </summary>
        private string fileName;
        /// <summary>
        /// Holds the project explorer item helper instance.
        /// </summary>
        private ProjectExplorerItemHelper projectExplorerHelper;
        /// <summary>
        /// Holds a reference to the adapted project package.
        /// </summary>
        private Package projectPackage;
        /// <summary>
        /// Holds the collection of resources in this project.
        /// </summary>
        private ObservableCollection<Resource> resources;
        /// <summary>
        /// Holds the collection of scores in this project.
        /// </summary>
        private ObservableCollection<Score> scores;
        /// <summary>
        /// Holds the name of the temporary stack file.
        /// </summary>
        private string stackFileName;
        /// <summary>
        /// Holds the temporary stack file.
        /// </summary>
        private System.IO.Packaging.Package stackPackage;
        /// <summary>
        /// Holds the name of the temporary copy of the project file that is currently open.
        /// </summary>
        private string tempFileName;

        #endregion
    }

    /// <summary>
    /// Identifies the root directory of different project item types.
    /// </summary>
    public enum RootDirectory
    {
        /// <summary>
        /// The root directory of score parts.
        /// </summary>
        Scores = 0,
        /// <summary>
        /// The root directory of resource parts.
        /// </summary>
        Resources = 1,
        /// <summary>
        /// The root directory of composition parts.
        /// </summary>
        Compositions = 2
    }
}
